package com.psqiu.coverflow.adapter;

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Shader;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

/**
 * This class has only internal use (package scope).
 * <p/>
 * It is responsible for applying additional effects to each coverflow item,
 * that can only be applied at view level (e.g. color saturation).
 * <p/>
 * This is a ViewGroup by intention to enable child views in layouts to stay
 * interactive (like buttons) though transformed.
 * <p/>
 * Since this class is only used within the FancyCoverFlowAdapter it doesn't
 * need to check if there are multiple children or not (there can only be one at
 * all times).
 */
@SuppressWarnings("ConstantConditions")
public class FancyCoverFlowItemWrapper extends ViewGroup {

	// =============================================================================
	// Private members
	// =============================================================================

	private float saturation;

	private boolean isReflectionEnabled = false;

	private float imageReflectionRatio;

	private int reflectionGap;

	private float originalScaledownFactor;

	/**
	 * This is a matrix to apply color filters (like saturation) to the wrapped
	 * view.
	 */
	private ColorMatrix colorMatrix;

	/**
	 * This paint is used to draw the wrapped view including any filters.
	 */
	private Paint paint;

	/**
	 * This is a cache holding the wrapped view's visual representation.
	 */
	private Bitmap wrappedViewBitmap;

	/**
	 * This canvas is used to let the wrapped view draw it's content.
	 */
	private Canvas wrappedViewDrawingCanvas;

	// =============================================================================
	// Constructor
	// =============================================================================

	public FancyCoverFlowItemWrapper(Context context) {
		super(context);
		this.init();
	}

	public FancyCoverFlowItemWrapper(Context context, AttributeSet attrs) {
		super(context, attrs);
		this.init();
	}

	public FancyCoverFlowItemWrapper(Context context, AttributeSet attrs,
                                     int defStyle) {
		super(context, attrs, defStyle);
		this.init();
	}

	private void init() {
		this.paint = new Paint();
		this.colorMatrix = new ColorMatrix();
		// TODO: Define a default value for saturation inside an XML.
		this.setSaturation(1);
	}

	// =============================================================================
	// Getters / Setters
	// =============================================================================

	void setReflectionEnabled(boolean hasReflection) {
		if (hasReflection != this.isReflectionEnabled) {
			this.isReflectionEnabled = hasReflection;

			if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
				// Turn off hardware acceleration if necessary (reflections
				// won't support it).
				this.setLayerType(hasReflection ? View.LAYER_TYPE_SOFTWARE
						: View.LAYER_TYPE_HARDWARE, null);
			}

			this.remeasureChildren();
		}
	}

	void setReflectionRatio(float imageReflectionRatio) {
		if (imageReflectionRatio != this.imageReflectionRatio) {
			this.imageReflectionRatio = imageReflectionRatio;
			this.remeasureChildren();
		}
	}

	void setReflectionGap(int reflectionGap) {
		if (reflectionGap != this.reflectionGap) {
			this.reflectionGap = reflectionGap;
			this.remeasureChildren();
		}
	}

	public void setSaturation(float saturation) {
		if (saturation != this.saturation) {
			this.saturation = saturation;
			this.colorMatrix.setSaturation(saturation);
			this.paint.setColorFilter(new ColorMatrixColorFilter(
					this.colorMatrix));
		}
	}

	// =============================================================================
	// Supertype overrides
	// =============================================================================

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		this.remeasureChildren();

		// If we have reflection enabled, the original image is scaled down and
		// a reflection is added beneath. Thus,
		// while maintaining the same height the width decreases and we need to
		// adjust measured width.
		// WARNING: This is a hack because we do not obey the EXACTLY
		// MeasureSpec mode that we will get mostly.
		if (this.isReflectionEnabled) {
			this.setMeasuredDimension(
					(int) (this.getMeasuredWidth() * this.originalScaledownFactor),
					this.getMeasuredHeight());
		}
	}

	@SuppressLint("DrawAllocation")
	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		if (changed) {
			int measuredWidth = this.getMeasuredWidth();
			int measuredHeight = this.getMeasuredHeight();

			if (this.wrappedViewBitmap == null
					|| this.wrappedViewBitmap.getWidth() != measuredWidth
					|| this.wrappedViewBitmap.getHeight() != measuredHeight) {
				this.wrappedViewBitmap = Bitmap.createBitmap(measuredWidth,
						measuredHeight, Bitmap.Config.ARGB_8888);
				this.wrappedViewDrawingCanvas = new Canvas(
						this.wrappedViewBitmap);
			}

			View child = getChildAt(0);
			int childWidth = child.getMeasuredWidth();
			int childHeight = child.getMeasuredHeight();
			int childLeft = (measuredWidth - childWidth) / 2;
			int childRight = measuredWidth - childLeft;
			child.layout(childLeft, 0, childRight, childHeight);
		}
	}

	@TargetApi(Build.VERSION_CODES.HONEYCOMB)
	@Override
	protected void dispatchDraw(Canvas canvas) {
		View childView = getChildAt(0);

		if (childView != null) {
			// If on honeycomb or newer, cache the view.
			if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
				if (childView.isDirty()) {
					childView.draw(this.wrappedViewDrawingCanvas);

					if (this.isReflectionEnabled) {
						this.createReflectedImages();
					}
				}
			} else {
				childView.draw(this.wrappedViewDrawingCanvas);
			}
		}

		canvas.drawBitmap(this.wrappedViewBitmap,
				(this.getWidth() - childView.getWidth()) / 2, 0, paint);
	}

	// =============================================================================
	// Methods
	// =============================================================================

	private void remeasureChildren() {
		View child = this.getChildAt(0);

		if (child != null) {
			// When reflection is enabled calculate proportional scale down
			// factor.
			final int originalChildHeight = this.getMeasuredHeight();
			this.originalScaledownFactor = this.isReflectionEnabled ? (originalChildHeight
					* (1 - this.imageReflectionRatio) - reflectionGap)
					/ originalChildHeight
					: 1.0f;
			final int childHeight = (int) (this.originalScaledownFactor * originalChildHeight);
			final int childWidth = (int) (this.originalScaledownFactor * getMeasuredWidth());

			int heightSpec = MeasureSpec.makeMeasureSpec(childHeight,
					MeasureSpec.AT_MOST);
			int widthSpec = MeasureSpec.makeMeasureSpec(childWidth,
					MeasureSpec.AT_MOST);
			this.getChildAt(0).measure(widthSpec, heightSpec);
		}
	}

	/**
	 * Creates the reflected images.
	 * 
	 * @return true, if successful
	 */
	private void createReflectedImages() {

		final int width = this.wrappedViewBitmap.getWidth();
		final int height = this.wrappedViewBitmap.getHeight();

		final Matrix matrix = new Matrix();
		matrix.postScale(1, -1);

		final int scaledDownHeight = (int) (height * originalScaledownFactor);
		final int invertedHeight = height - scaledDownHeight - reflectionGap;
		final int invertedBitmapSourceTop = scaledDownHeight - invertedHeight;
		final Bitmap invertedBitmap = Bitmap.createBitmap(
				this.wrappedViewBitmap, 0, invertedBitmapSourceTop, width,
				invertedHeight, matrix, true);

		this.wrappedViewDrawingCanvas.drawBitmap(invertedBitmap, 0,
				scaledDownHeight + reflectionGap, null);

		final Paint paint = new Paint();
		final LinearGradient shader = new LinearGradient(0, height
				* imageReflectionRatio + reflectionGap, 0, height, 0x70ffffff,
				0x00ffffff, Shader.TileMode.CLAMP);
		paint.setShader(shader);
		paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
		this.wrappedViewDrawingCanvas.drawRect(0, height
				* (1 - imageReflectionRatio), width, height, paint);
	}
}
